package main

import (
	"encoding/json"
	"fmt"
	"gopkg.in/gcfg.v1"
	"log"
	"log/slog"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"
)

type Config struct {
	Base struct {
		Username string
		Password string
		VpnName  string
		Psk      string
	} `cfg:"base"`
}

var config Config
var serverDomains []string

func init() {
	http.DefaultClient.Timeout = time.Second * 5

	iniBytes, err := os.ReadFile("conf/config.ini")
	if err != nil {
		log.Fatalln("conf/config.ini file no exist !!!")
	}
	err = gcfg.ReadStringInto(&config, string(iniBytes))
	if err != nil {
		fmt.Println(err)
		return
	}
	slog.Info(fmt.Sprint("配置：", config))

	file, err := os.ReadFile("conf/server_domains.txt")
	if err != nil {
		log.Fatalln("conf/server_domains.txt file no exist !!!")
	}
	content := string(file)
	lines := strings.Split(content, "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "#") {
			continue
		}
		serverDomains = append(serverDomains, line)
	}
	if len(serverDomains) == 0 {
		log.Fatalln("serverDomains 域名数量必须 > 0")
	}
	slog.Info(fmt.Sprint("服务器域名数量：", len(serverDomains)))
}

type L2TPClient struct {
	ConnectionName string
	Username       string
	Password       string
	ServerAddress  string
	PSK            string
	ReconnectDelay time.Duration
}

func NewL2TPClient(connName, username, password, serverAddress, psk string) *L2TPClient {
	return &L2TPClient{
		ConnectionName: connName,
		Username:       username,
		Password:       password,
		ServerAddress:  serverAddress,
		PSK:            psk,
		ReconnectDelay: 5 * time.Second,
	}
}

func (l *L2TPClient) CreateVPNConnection() error {
	if l.IsVPNConnectionExists() {
		log.Printf("VPN连接 '%s' 已存在", l.ConnectionName)
		return nil
	}

	// 使用 PowerShell 命令创建连接（兼容家庭版和专业版）
	psScript := fmt.Sprintf(
		"Add-VpnConnection -Name '%s' -ServerAddress '%s' -TunnelType L2tp -L2tpPsk '%s' -EncryptionLevel Required -Force; ",
		l.ConnectionName, l.ServerAddress, l.PSK,
	)
	createCmd := exec.Command("powershell", "-Command", psScript)
	output, err := createCmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("创建VPN连接失败: %v, 输出: %s", err, string(output))
	}

	log.Printf("成功创建VPN连接 '%s'", l.ConnectionName)
	return nil
}
func (l *L2TPClient) IsVPNConnectionExists() bool {
	cmd := exec.Command("powershell", "-Command", fmt.Sprintf("Get-VpnConnection -Name '%s'", l.ConnectionName))
	output, err := cmd.CombinedOutput()
	return err == nil && strings.Contains(string(output), l.ConnectionName)
}

func (l *L2TPClient) Connect() error {
	// 确保 VPN 连接存在
	if err := l.CreateVPNConnection(); err != nil {
		return err
	}

	// 连接 VPN
	cmd := exec.Command("rasdial", l.ConnectionName, l.Username, l.Password)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("连接失败: %v, 输出: %s", err, string(output))
	}
	return nil
}

func (l *L2TPClient) Disconnect() error {
	cmd := exec.Command("rasdial", l.ConnectionName, "/disconnect")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("断开连接失败: %v, 输出: %s", err, string(output))
	}
	return nil
}

func (l *L2TPClient) IsConnected() bool {
	cmd := exec.Command("rasdial")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return false
	}
	return strings.Contains(string(output), l.ConnectionName)
}

func (l *L2TPClient) AutoReconnect() {
	for {
		if !l.IsConnected() {
			log.Printf("检测到连接断开，尝试重新连接...")
			if err := l.Connect(); err != nil {
				log.Printf("重连失败: %v", err)
			} else {
				ip, _ := GetPublicIp()
				log.Printf("重连成功,当前IP: %s,地区: xx/xx", ip)
			}
		}
		time.Sleep(l.ReconnectDelay)
	}
}

func GetPublicIp() (string, error) {
	type IPResponse struct {
		Origin string `json:"origin"`
	}
	resp, err := http.Get("https://httpbin.org/ip")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	var ipResp IPResponse
	if err := json.NewDecoder(resp.Body).Decode(&ipResp); err != nil {
		return "", err
	}
	return ipResp.Origin, nil
}

func main() {
	// 创建 SSTP 客户端实例
	client := NewL2TPClient(
		config.Base.VpnName,  // 替换为你的 VPN 连接名称
		config.Base.Username, // 替换为你的用户名
		config.Base.Password, // 替换为你的密码
		serverDomains[0],
		config.Base.Psk,
	)

	// 启动自动重连
	go client.AutoReconnect()

	// 保持程序运行
	select {}
}
